package me.spring.cloud.components.starter.rpc.crypto.handler;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import lombok.extern.slf4j.Slf4j;
import me.spring.cloud.common.components.rpc.crypto.annotation.EncryptBody;
import me.spring.cloud.common.components.rpc.crypto.annotation.IgnoreEncryptBody;
import me.spring.cloud.common.components.rpc.crypto.constants.CommonConstants;
import me.spring.cloud.common.components.rpc.crypto.service.CryptoService;
import me.spring.cloud.common.components.rpc.response.Response;
import me.spring.cloud.common.components.util.json.JsonUtil;
import me.spring.cloud.common.components.util.lang.StringUtil;
import me.spring.cloud.components.starter.rpc.crypto.properties.SecretProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * @author luffy
 * @description: 全局统一加密response
 * @date 2019-11-15 14:04
 */
@Slf4j
@Order(2)
@RestControllerAdvice
@ConditionalOnProperty(prefix = CommonConstants.SECRET_PROPERTIES_RESPONSE_PREFIX, name = "enabled", havingValue = "true")
public class EncryptResponseHandler implements ResponseBodyAdvice<Object> {

  @Autowired
  private CryptoService cryptoService;

  @Resource
  private SecretProperties secretProperties;

  @Autowired
  private HttpServletRequest httpServletRequest;

  private AntPathMatcher matcher = new AntPathMatcher();

  /**
   * Whether this component supports the given controller method return type and the selected {@code
   * HttpMessageConverter} type.
   * @param returnType the return type
   * @param converterType the selected converter type
   * @return {@code true} if {@link #beforeBodyWrite} should be invoked; {@code false} otherwise
   */
  @Override
  public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
    //判断支持的类型，因为我们定义的CommonResponse 里面的body可能是任何类型，这里就不判断统一放过
    //如果你想对执行的返回体进行操作，可将上方的Object换成你自己的类型
    if (secretProperties.isScanAnnotation()) {
      return returnType.hasMethodAnnotation(EncryptBody.class) && !ignoreEncry();
    } else {
      return !returnType.hasMethodAnnotation(IgnoreEncryptBody.class) && !ignoreEncry();
    }
  }

  /**
   * 验证是否请求头信息中是否包含过滤头信息
   * @return false:需要加密，true:不需要加密
   */
  private boolean ignoreEncry(){
    if(!secretProperties.getResponse().isEnabled() ){
     return true;
    }
    if(null != secretProperties.getResponse().getIgnoreUrls()
            && !secretProperties.getResponse().getIgnoreUrls().isEmpty()
            && secretProperties.getResponse().getIgnoreUrls().stream()
            .anyMatch(s -> matcher.match(s, httpServletRequest.getRequestURI()))){
      return true;
    }
    if(StringUtil.isNotEmpty(secretProperties.getResponse().getIgnoreHeaderName())
            && StringUtil.isNotEmpty(secretProperties.getResponse().getIgnoreHeaderValue())){
      String headerValue = httpServletRequest.getHeader(secretProperties.getResponse().getIgnoreHeaderName());
      if(StringUtil.isEmpty(headerValue)) {
        return false;
      }
      return secretProperties.getResponse().getIgnoreHeaderValue()
              .contains(headerValue);
    }
    return false;
  }

  /**
   * Invoked after an {@code HttpMessageConverter} is selected and just before its write method is invoked.
   * @param body the body to be written
   * @param returnType the return type of the controller method
   * @param selectedContentType the content type selected through content negotiation
   * @param selectedConverterType the converter type selected to write to the response
   * @param request the current request
   * @param response the current response
   * @return the body that was passed in or a modified (possibly new) instance
   */
  @Override
  public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
      Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
      ServerHttpResponse response) {
    // 加密
    Response httpResponse;
    if (body instanceof Response) {
      httpResponse = (Response) body;
      if (null != httpResponse.getBody()) {
        httpResponse.setBody(cryptoService.encrypt(httpResponse.getBody()));
        return httpResponse;
      } else {
        return body;
      }
    }
    return cryptoService.encrypt(JsonUtil.toJSON(body));
  }
}
